import {
  CENTEREVENTS,
  ColorSchema,
  CSLine,
  MouseInfo,
  ParameterObjectType,
} from '@amcax/base';
import { CSS2DReactObject } from '@app-base/common/components/css-2d-react-object/CSS2DReactObject';
import { Object3D, Vector3, Vector3Tuple } from 'three-legacy';
import { DistanceLine } from './DistanceLine';
import { ctx2 } from '@app-base/module';
import { MouseEventHandler } from 'react';
import { pickInput3dLock } from '@app-base/common/components/pick-input/3d';
import { AngleLabel } from './Angle';
import { MoreOutlined } from '@ant-design/icons';
import { getCSSColorByNumber, useMeasureText } from '.';
import { MeasureChildObject } from './ChildObject';
import { measureRenderManager } from '@app-base/manager/MeasureRenderManager';

const ReactMeasureLabel = ({
  value,
  label,
  type,
  onDragStart,
  labelColor,
  visible,
  uuid,
}: {
  value: number;
  label: string;
  type: ParameterObjectType | 'Area';
  onDragStart: MouseEventHandler<HTMLDivElement>;
  labelColor?: string;
  visible: boolean;
  uuid: string;
}) => {
  const text = useMeasureText(value, type);

  return (
    <div
      id={`${MeasureLabel.domIDPrefix}${uuid}`}
      onPointerDown={(e) => {
        onDragStart(e);
      }}
      style={{
        boxShadow:
          '0px 4px 15px rgba(53, 72, 83, 0.1), 0px 1px 4px rgba(53, 72, 83, 0.05)',
        display: visible ? null : 'none',
      }}
      className='bg-white rounded pr-2 flex justify-center items-center cursor-move h-10'
    >
      <MoreOutlined />
      <div className='rounded-l h-full flex justify-center items-center border-l border-gray-400'>
        {labelColor && (
          <div
            className='w-2 h-[calc(100%_-_2px)] rounded-l mx-0.5'
            style={{
              backgroundColor: labelColor,
            }}
          />
        )}
        <div className='ml-2'>{label}</div>
        <div className='w-px h-full bg-gray-200 mx-2' />
        <div className='cursor-auto h-full flex items-center'>{text}</div>
      </div>
    </div>
  );
};

export type MeasureLabelAttrs = {
  value: number;
  label: string;
  type: ParameterObjectType | 'Area';
  position: Vector3Tuple;
  labelColor?: number;
  // 与其关联的Object3D
  lineTo?: Object3D;
  // 与其关联的被拾取对象
  pickTargetID?: string;
  labelPosition?: Vector3Tuple;
};

export class MeasureLabel extends MeasureChildObject {
  static domIDPrefix = 'measure-label_';
  cssObj: CSS2DReactObject;
  indicateLine: CSLine;
  startPosition: Vector3;
  endPosition: Vector3;

  private dragStart: MouseInfo;

  constructor(attrs: MeasureLabelAttrs) {
    super();

    const element = document.createElement('div');

    this.startPosition = new Vector3(...attrs.position);
    this.endPosition = new Vector3(...(attrs.labelPosition ?? attrs.position));

    const screenPoint = ctx2.viewer.sceneSetup.modelToScreen(
      new Vector3(...attrs.position),
    );

    const screenCenter = new Vector3(
      ctx2.container.clientWidth / 2,
      ctx2.container.clientHeight / 2,
      0,
    );

    if (attrs.lineTo instanceof DistanceLine) {
      const vertexes: [Vector3, Vector3] = attrs.lineTo.getVertexs();
      const planeNormal = ctx2.viewer.sceneSetup.getScreenNormal();

      const vector = planeNormal
        .cross(vertexes[0].clone().sub(vertexes[1]))
        .normalize()
        .multiplyScalar(attrs.value * 0.75);

      const isPointOut =
        screenPoint
          .clone()
          .sub(screenCenter)
          .dot(ctx2.viewer.sceneSetup.modelToScreen(vector)) > 0;

      if (attrs.labelPosition == null) {
        this.endPosition.add(isPointOut ? vector : vector.negate());
      }
    }

    if (attrs.lineTo instanceof AngleLabel) {
      const vertexes: [Vector3, Vector3] = [
        attrs.lineTo.vertexs[0],
        attrs.lineTo.vertexs.at(-1),
      ];

      const distance = vertexes[0].clone().sub(vertexes[1]).length();

      const planeNormal = ctx2.viewer.sceneSetup.getScreenNormal();

      const vector = planeNormal
        .cross(vertexes[0].clone().sub(vertexes[1]))
        .normalize()
        .multiplyScalar(distance * 1.3);

      const isPointOut =
        screenPoint
          .clone()
          .sub(screenCenter)
          .dot(ctx2.viewer.sceneSetup.modelToScreen(vector)) > 0;

      if (attrs.labelPosition == null) {
        this.endPosition.add(isPointOut ? vector : vector.negate());
      }
      this.endPosition.add(isPointOut ? vector : vector.negate());
    }

    this.cssObj = new CSS2DReactObject(element, ReactMeasureLabel, {
      value: attrs.value,
      type: attrs.type,
      label: attrs.label,
      onDragStart: (e) => {
        this.dragStart = {
          x: e.nativeEvent.clientX,
          y: e.nativeEvent.clientY,
        };

        this.oldEndPosition = this.endPosition;

        pickInput3dLock.lock();
      },
      uuid: this.getRootObject()?.uuid,
      labelColor: getCSSColorByNumber(attrs.labelColor),
      visible: true,
    });

    this.cssObj.position.set(...this.endPosition.toArray());
    this.add(this.cssObj);

    this.indicateLine = new CSLine({
      vertexs: [new Vector3(...attrs.position), this.endPosition],
      lineWidth: 1,
      color: 0x000000,
    });

    // it's hack
    this.indicateLine.onAfterRender = () => {
      if (this.rootObject == null) {
        this.cssObj.updateProps({
          ...this.cssObj.props,
          uuid: this.getRootObject()?.uuid,
        });
      }
    };

    this.add(this.indicateLine);

    this.onPointerUp = () => {
      if (this.dragStart != null) {
        measureRenderManager.isDraggingLabel = false;
      }
      this.dragStart = null;
      this.dragStartInWorld = null;
      pickInput3dLock.unlock();
    };

    this.onPointerMove = (e: PointerEvent) => {
      if (this.dragStart == null) {
        return;
      }

      measureRenderManager.isDraggingLabel = true;

      const sceneSetup = ctx2.viewer.sceneSetup;

      if (this.dragStartInWorld == null) {
        this.dragStartInWorld = sceneSetup
          .screenToWorld(
            this.dragStart.x,
            this.dragStart.y,
            sceneSetup.getCameraNormal().clone().normalize(),
            new Vector3(0, 0, 0),
          )
          .clone();
      }

      const endPositionInWorld = sceneSetup.screenToWorld(
        e.clientX,
        e.clientY,
        sceneSetup.getCameraNormal().clone().normalize(),
        new Vector3(0, 0, 0),
      );

      this.endPosition = this.oldEndPosition
        .clone()
        .add(endPositionInWorld.clone().sub(this.dragStartInWorld));

      this.updateRender();
    };

    document.addEventListener(CENTEREVENTS.POINTERUP, this.onPointerUp);
    document.addEventListener(CENTEREVENTS.POINTERMOVE, this.onPointerMove);
  }

  onPointerUp?: VoidFunction;

  private dragStartInWorld?: Vector3;
  private oldEndPosition?: Vector3;

  onPointerMove?: (e: PointerEvent) => void;

  private updateRender() {
    this.cssObj.position.set(...this.endPosition.toArray());
    this.indicateLine.update({
      vertexs: [this.startPosition, this.endPosition],
    });

    ctx2.viewer.requestRender();
  }

  renderHighlight() {
    let color = 0x000000;
    if (this.isHovered) {
      color = ColorSchema.hover.edge;
    } else if (this.isSelected) {
      color = ColorSchema.select.edge;
    }

    this.indicateLine.setColor(color);
  }

  setHover(val: boolean): void {
    super.setHover(val);
  }

  destroy() {
    this.parent?.remove(this);
    this.remove(this.cssObj);
    this.indicateLine?.destroy();

    pickInput3dLock.unlock();

    document.removeEventListener(CENTEREVENTS.POINTERUP, this.onPointerUp);
    document.removeEventListener(CENTEREVENTS.POINTERMOVE, this.onPointerMove);
  }
}
